package cz.matasek.dbLogger.model;

import cz.matasek.dbLogger.Constant;
import cz.matasek.dbLogger.Util;
import cz.matasek.dbLogger.sql.SQLKeyword;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * The class represents a container for triggering events of any database
 * trigger. It also supports <strong>on update</strong> triggers.
 *
 * @author Luboš Matásek
 */
public final class TriggeringEvents {

    /**
     * Enumeration of all DML triggering events.
     */
    public enum Event {

        /**
         * Insert event.
         */
        INSERT,
        /**
         * Update event.
         */
        UPDATE,
        /**
         * Delete event.
         */
        DELETE;
    }
    /**
     * Set of events, which fires the trigger.
     */
    private Set<Event> triggeringEvents;
    /**
     * Set of columns, whose updates fires the trigger.
     */
    private Set<String> onUpdateColumns;

    /**
     * Creates a new empty <code>TriggeringEvents</code> object.
     */
    public TriggeringEvents() {
        triggeringEvents = new HashSet<Event>();
        onUpdateColumns = new HashSet<String>();
    }

    /**
     * Sets the specified event to become triggering.
     *
     * @param event the triggering event
     */
    public void setEvent(Event event) {
        triggeringEvents.add(event);
    }

    /**
     * Checks if the specified event is set to be logged.
     *
     * @param event the triggering event to be checked
     * @return <code>true</code> if the event is triggering; <code>false</code>
     * otherwise
     */
    public boolean hasEvent(Event event) {
        return triggeringEvents.contains(event);
    }

    /**
     * Returns the triggering events for this object.
     *
     * @return set of triggering events
     */
    public Set<Event> getEvents() {
        return new HashSet<Event>(triggeringEvents);
    }

    /**
     * Sets columns, whose updates fires the trigger. Update event has to be set
     * explicitly.
     * 
     * @param onUpdateColumns collection of columns, which fires on update
     * triggers
     */
    public void setOnUpdateColumns(Collection<String> onUpdateColumns) {
        this.onUpdateColumns.clear();
        this.onUpdateColumns.addAll(onUpdateColumns);
    }

    /**
     * Returns columns, whose updates fires the trigger.
     *
     * @return columns, whose updates fires the trigger
     */
    public Collection<String> getOnUpdateColumns() {
        return new HashSet<String>(onUpdateColumns);
    }

    /**
     * Clears list of columns, which fires the trigger.
     */
    public void clearOnUpdateColumns() {
        onUpdateColumns.clear();
    }

    /**
     * Returns SQL clause defining triggering events saved in this class.
     * The clause contains list of triggering events separated by
     * <code>OR</code> keyword (e.g. <code>INSERT OR UPDATE</code>). If
     * on update trigger is allowed it consists of <code>UPDATE OF</code>
     * clause followed by comma-separated list of on update columns (e.g.
     * <code>UPDATE OF first_column, second_column</code>).
     *
     * @return SQL clause defining triggering events
     */
    public String getSQL() {
        // creates map of SQL clauses for particular triggering events
        Map<Event, String> partialClauses = new HashMap<Event, String>();
        for (Event triggeringEvent : triggeringEvents) {
            partialClauses.put(triggeringEvent, triggeringEvent.name());
        }

        // if on update columns are set, update clause must be replaced
        if (hasEvent(Event.UPDATE) && !onUpdateColumns.isEmpty()) {
            partialClauses.put(Event.UPDATE,
                    Event.UPDATE.name() + " " + SQLKeyword.OF + " " +
                    Util.implode(Constant.COMMA, onUpdateColumns.toArray()));
        }

        // returns the result
        return Util.implode(" " + SQLKeyword.OR + " ", partialClauses.values().toArray());
    }

    /**
     * Checks if this object contains on update triggering event.
     *
     * @return <code>true</code> if on update event is present;
     * <code>false</code> otherwise
     */
    public boolean hasOnUpdateEvent() {
        if (hasEvent(Event.UPDATE) && !onUpdateColumns.isEmpty()) {
            return true;
        } else {
            return false;
        }
    }
}
